Convert a String to Another String Using the Bitwise XOR Operation
Convert a String to Another String Using the Bitwise XOR Operation

So, curiosity has gotten the best of you? You want to know what a bitwise exclusive or operator is? Maybe you already know and are thinking, "This isn't how you use that"? Perhaps you just want to see how dumb this can be. Please, allow me to take you on a magical journey of pointlessness and discovery.

What even is this?

This is a demonstration of how to use the bitwise exclusive or operator to convert one string to another and the process that goes into doing so.

Why would you ever do that?
Responsive image

Really, you wouldn't. Or, at least I thought you wouldn't when I first wrote this. It turns out, while the way I originally approached it isn't very useful, the concepts behind my approach can actually be very useful. Mainly, this serves as a way to perform low level encryption. So, let's start by breaking down the less useful way I approached the bitwise exclusive or (XOR) operator, and see how my misinterpretations ended up leading to a fairly interesting discovery.

So, what are we going to be doing? We will take 2 strings, break them down to decimal values, break them down again to binary values, then use a few extra steps to rebuild our initial string to the string we wish to convert it to. Sounds a little complicated?

I don't know? That sounds pretty stupid.
Responsive image

It is pretty stupid, but it turns out there's smart stuff shuffled around in all the stupid. So, let's start! What's a bitwise exclusive or operator? It's purpose is to take two binary values and compare them. The return of this comparison results in a 1 anywhere the two binary values do not match, and a 0 where they do. The XOR operator is signified by a caret (^) in the majority of programming languages, if not all of them.

Alright, how about an example?

Alright, let's break this down. We'll start with 2 characters p and s. p is represented by the binary value 1110000. s is represented by the binary value 1110011. So when we use the bitwise XOR operator, we are essentially writing it as p ^ s. There is another step involved, but we'll hold off on that for now. In the meantime, we will say p ^ s, and the computer will see this as 1110000 ^ 1110011. The computer will then compare these 2 binary numbers to see which bits are the same, and which ones are different. In this case, 11100 are all the same. This will produce five 0 bits. The last two bits of p and s are different. This will produce two 1 bits. That makes the final return of 1110000 ^ 1110011 equals 0000011 or simply 11. Unfortunately, 11 is difficult to represent as an ASCII character as it stands for "End of Text". ASCII conversion aside, this still makes for a decent example as to how bitwise XOR operates.

ASCII Character and XOR Operation Binary Value
p 1110000
s 1110011
p ^ s 0000011
That's pretty neat I guess, but I was promised string conversions!
Responsive image

Right you are! Let's waste some time converting strings with XOR. We've got some of the building blocks in the table above, but it doesn't really paint the entire picture. For the purpose of this, we're going to start with two strings that I've chosen: peach and shift. Why these two? I like Princess Peach, we're shifting some bits around, they both have 5 characters. Sky's the limit here when it comes to picking reasons why.

As stated before, the XOR operator does a comparison of two binary values and returns true (1) or false (0) depending on if the bits in those two values are different or the same. We have to do a little bit of hoop jumping to take a string, break it down to binary values for each individual character, then rebuild those binary values as our intended result. We'll start with our strings, peach and shift. Both strings have to be broken down to individual characters. The way I've done this is using a for in loop and a counter. But there's lots of other, likely better ways to do this. You could use the string .split() function or a normal for loop to go over each character. It's really up to you as long as we have access to the individual characters of our two strings.

You can see in the example code below how I've structured my for in loop. The important thing to note is that my counter is set up in a manner that allows it to loop back on itself, starting at 0 all over again in case the first string has more characters than the second. This doesn't affect how the bitwise XOR operator works, but it will come into play when we get to the part where we use this concept to create a form of encryption.


  let stringOne = "peach";
  let stringTwo = "shift";
  let counter = 0;
  let stringTwoLen = stringTwo.length - 1;

  for (let s in stringOne){

    //counter is reset to 0 when the length of stringOne
    //exceeds the length of stringTwo, forcing the
    //conversion to start over from the beginning for the
    //remaining characters in stringOne
    counter === stringTwoLen ? counter = 0 : counter++;
  }
          

Now that we have both strings separated out we can start our bitwise XOR operation on the characters right? That's a big negative. Well, at least in JavaScript that's a big negative. To perform a bitwise XOR operation on a string, we need to convert the string character(s) to a decimal value. A decimal value is a numerical representation for an ASCII character. In our case, p will become 112, and s will become 115. We can find the decimal value of a character in JavaScript using the .charCodeAt(index) function, where index is the location of our character in the string. Since we've broken down our strings into single characters, our index will always be 0.


  for (let s in stringOne){
    //for our "peach shift" example, we'll base this on the first
    //characters of peach and shift.
    //stringOne[s] = p
    let stringOneDecimal = stringOne[s].charCodeAt(0); //112
    //stringTwo[counter] = s
    let stringTwoDecimal = stringTwo[counter].charCodeAt(0); //115

    counter === stringTwoLen ? counter = 0 : counter++;
  }
            

Cool. We've finally reached our first XOR operation. Similar to the above example, we'll now take the decimal values, do a bitwise XOR comparison on them and receive a result. Sticking with p and s, we ended up with an operation that looks like 112 ^ 115. And like before, this breaks down further to 1110000 ^ 1110011, leaving us once again with a final result of 11.


    let stringOneDecimal = stringOne[s].charCodeAt(0); //112
    let stringTwoDecimal = stringTwo[counter].charCodeAt(0); //115

    //We get our conversion decimal value for coverting
    //stringOne to stringTwo with XOR
    //No need to covert to binary as XOR will do this
    //with the decimal values
    let firstXORDecimal = stringOneDecimal ^ stringTwoDecimal; //3
            
We did it! Mission accomplished.
Responsive image

We've just gotten started with how overly complicated this gets. Now we need to take the first character of our initial string and do a secondary bitwise XOR operation with the result of our first bitwise XOR operation. That's right, we're doubling down! We start with the decimal value of p from peach which is 112. Next We take our result from our first XOR operation 11, except 11 is the binary value the computer sees from comparing the bits between the two decimal values. What we will see is the decimal value represented by 11, which is 3. As stated before, 11 or in this case 3 is the ASCII representation for "End of Text" which isn't really easy to show as an ASCII character. Now that we have these two decimal values, we do our second bitwise XOR operation: 112 ^ 3. Naturally, the computer breaks this down to a binary operation of 1110000 ^ 0000011 which results in 1110011. And you know what that converts to?


    let stringOneDecimal = stringOne[s].charCodeAt(0); //112
    let stringTwoDecimal = stringTwo[counter].charCodeAt(0); //115
    let firstXORDecimal = stringOneDecimal ^ stringTwoDecimal; //3

    //Now we redo the operation to get back to the decimal
    //value from stringTwo, or the string we wish to convert to
    let secondXORDecimal = stringOneDecimal ^ firstXORDecimal; //115
            
It's in the code example. It's 115.

THAT'S RIGHT! We're back at decimal value 115. In JavaScript, we can use the string function String.fromCharCode(Decimal Value) to convert 115 back to the ASCII character we started with, s. And through a needlessly complicated and incredibly stupid method, we've converted one character from one string to another character of another string. Now, we just repeat the process for each character. I've chosen to push each character into an array, however you may go about rebuilding the converted string another way. I've used the array function join("") to recombine all of the characters back to a single string. Our end result being the string we wanted to convert peach to, shift.


    //String.fromCharCode() allows us to convert the decimal back
    //to an ASCII character
    let convertedCharacter = String.fromCharCode(secondXORDecimal); //s
            
Is there any way to visualize this process?

There sure is! I've built this working example that will allow you to view how one string is processed and converted to the other. I've also included the function I've used to make the conversion happen.


    function peachShift(originalString, covertString){
      let stringOne = originalString.toString();
      let stringTwo = covertString.toString();
      let counter = 0;
      let stringTwoLen = stringTwo.length - 1;
      let finalString = [];

      for (let s in stringOne){
        let stringOneDecimal = stringOne[s].charCodeAt(0);
                let stringTwoDecimal = stringTwo[counter].charCodeAt(0);
        let firstXORDecimal = stringOneDecimal ^ stringTwoDecimal;
        let secondXORDecimal = stringOneDecimal ^ firstXORDecimal;
        let convertedCharacter = String.fromCharCode(secondXORDecimal);

        finalString.push(convertedCharacter);

        counter === stringTwoLen ? counter = 0 : counter++;
      }

      return finalString.join("");
    }
            
Wow, that's kind of dumb.

It sure is! But, remember how I mentioned the concepts behind this can be useful, particularly when it comes to encryption? Well, let's take a quick look at how we can build a small encryption app using the bitwise XOR operator.

Responsive image

I won't break this down as much since the core concepts are basically the same. We will take a string and convert it using another string. However, this time we are going to be taking a message we wish to encrypt, and encoding it using a password or passphrase we set. That password or passphrase will then be used to decrypt our encrypted message.

There are two big differences that will need to occur to encrypt our message. The first is that we will no longer be converting to another string. Instead we are encrypting with another string. So, in the case of peach-shift, peach will still be our starting string, however shift will not be our end result. Our end result will be the ASCII characters we get from the decimal number that is the result of the bitwise XOR operator. So again, we start with p or 112. We will perform a bitwise XOR operation with s or 115, and will get the result of 3 or End of Text.

Now for the second change. Remember how I previously mentioned that End of Text was difficult to represent as an ASCII character? Well 3 isn't the only decimal that is hard to represent as an ASCII character. 0 through 31 are sometimes referred to as control characters, which typically perform some sort of control operation and don't really have a way to be easily represented as readable text. That's all well and good for encryption, however it poses a large problem for decryption.

It's difficult to decrypt an ASCII character that has no visible representation. So, the solution I came up with was to multiply the decimal character by 10 before converting it back to ASCII. That allows us to have a viewable character to copy and paste and send as an encrypted message, and it gives us a way to reverse the process to decrypt the message.

Here's a look at the encryption function I wrote. If you compare it to the peach shift function, you'll note that they are nearly identical.


    function peachEncrypt(){
      let message = document.getElementById('originalMessage').value;
    	let key = document.getElementById('encryptionKey').value;
      let messageString = message.toString();
      let keyString = key.toString();
      let counter = 0;
      let keyLen = keyString.length - 1;
      let finalString = [];

      for (let s in messageString){
        let messageStringDecimal = messageString[s].charCodeAt(0);
        let shifted = messageStringDecimal * 10;
        let keyStringDecimal = keyString[counter].charCodeAt(0);
        let firstXORDecimal = shifted ^ keyStringDecimal;
        let convertedCharacter = String.fromCharCode(firstXORDecimal);

        finalString.push(convertedCharacter);

        counter === keyLen ? counter = 0 : counter++;
      }

      return finalString.join("");
    }
            

And here's a functioning version of the encryption app.

Tap the Encrypted Message box to copy

Cool, so how do we decrypt the message?

It's basically the exact same process. The major difference is that for encryption, we want to shift our decimal value by multiplying it after we've done our bitwise XOR operation. For decryption, we want to divide the decimal value by 10 before we do the bitwise XOR operation. This allows us to get the decimal value of each character from the original message. Then all we have to do is convert from decimal to ASCII and we have the original message again.

Here's my decryption function. Again, it's very similar to the peach shift function, and almost identical to the encryption function.


    function peachDecrypt(){
      let message = document.getElementById('messageToDecrypt').value;
    	let key = document.getElementById('decryptionKey').value;
      let messageString = message.toString();
      let keyString = key.toString();
      let counter = 0;
      let keyLen = keyString.length - 1;
      let finalString = [];

      for (let s in messageString){
        let messageStringDecimal = messageString[s].charCodeAt(0);
        let keyStringDecimal = keyString[counter].charCodeAt(0);
        let firstXORDecimal = messageStringDecimal ^ keyStringDecimal;
    		let shifted = firstXORDecimal / 10;
        let convertedCharacter = String.fromCharCode(shifted);

        finalString.push(convertedCharacter);

        counter === keyLen ? counter = 0 : counter++;
      }

      return finalString.join("");
    }
            

And here's a functioning version of the decryption app.

Tap the Decrypted Message box to copy

Not bad. Not bad at all.

I don't think so. It actually turned out to be something kind of cool when all is said and done. There's still a lot of room for tweaking, improving, and modifying to build a better encryption app, or maybe something else new and interesting with the bitwise exclusive or operator. Hopefully reading through all of this has been somewhat informative. Most importantly, I hope this serves as an example that it doesn't matter what you set out to code. Even the dumbest idea can lead to some cool and interesting discoveries.

So, keep coding, and keep having fun while you do it!